package yunjiao.javatutorials.guava.concurrent.log;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 缓存系统锁监控
 *
 * @author yangyunjiao
 */
public class CacheSystem<K, V> {
    private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<K, LoggingLock> keyLocks = new ConcurrentHashMap<>();
    private final LoggingLock globalLock = new LoggingLock(new ReentrantLock(), "GlobalCacheLock");

    public V get(K key) {
        // 先尝试无锁读取
        V value = cache.get(key);
        if (value != null) {
            return value;
        }

        // 缓存未命中，使用键级锁
        LoggingLock keyLock = keyLocks.computeIfAbsent(key,
                k -> new LoggingLock(new ReentrantLock(), "KeyLock-" + k));

        keyLock.lock();
        try {
            // 双重检查
            value = cache.get(key);
            if (value == null) {
                // 模拟从数据库加载
                System.out.println("缓存未命中，从数据库加载: " + key);
                value = loadFromDatabase(key);
                cache.put(key, value);
            }
            return value;
        } finally {
            keyLock.unlock();
            // 清理不再需要的键锁
            keyLocks.remove(key, keyLock);
        }
    }

    public void clear() {
        globalLock.lock();
        try {
            cache.clear();
            keyLocks.clear();
            System.out.println("缓存已清空");
        } finally {
            globalLock.unlock();
        }
    }

    @SuppressWarnings("unchecked")
    private V loadFromDatabase(K key) {
        try {
            Thread.sleep(100); // 模拟数据库访问延迟
            return (V) ("ValueFor_" + key);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        CacheSystem<String, String> cache = new CacheSystem<>();
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // 模拟并发访问相同键
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 2; j++) {
                final String key = "key" + i;
                executor.submit(() -> {
                    String value = cache.get(key);
                    System.out.println(Thread.currentThread().getName() + " 获取 " + key + " = " + value);
                });
            }
        }

        Thread.sleep(2000);
        cache.clear();

        executor.shutdown();
    }
}
